package room

import (
	"errors"
	"server/common"
	"time"

	"github.com/jiyeyuran/mediasoup-go/v2"
	"github.com/mitchellh/mapstructure"
	"github.com/sirupsen/logrus"
)

// mediasoup router
type Router struct {
	RouterId  string                    // Unique identifier for the router
	RoomId    string                    // Room ID to which the router belongs
	ServiceId string                    // Node ID where the router is hosted
	WorkerId  int                       // Worker ID where the router is hosted
	RtpCaps   mediasoup.RtpCapabilities // RTP capabilities of the router
}

type Producer struct {
	ProducerId    string                  // Unique identifier for the producer
	Media         common.MediaInfo        // Media information for the producer
	Producing     bool                    // Indicates if the producer is currently producing media
	CreatedAt     int64                   // Timestamp when the producer was created
	RtpParameters mediasoup.RtpParameters // RTP parameters for the producer
}

type Consumer struct {
	ProduceUserId string                  // User ID of the producer
	ProducerId    string                  // Unique identifier for the producer
	ConsumerId    string                  // Unique identifier for the consumer
	Media         common.MediaInfo        // Media information for the consumer
	CreatedAt     int64                   // Timestamp when the consumer was created
	RtpParameters mediasoup.RtpParameters // RTP parameters for the consumer
}

type Transport struct {
	TransportType  string
	TransportId    string
	IceParameters  *mediasoup.IceParameters
	IceCandidates  []mediasoup.IceCandidate
	DtlsParameters *mediasoup.DtlsParameters
	SctpParameters *mediasoup.SctpParameters
}

type RoomUser struct {
	UserId        string                    // User ID of the participant
	SendTransport *Transport                // Send transport for this user
	RecvTransport *Transport                // Receive transport for this user
	ConnId        string                    // Connection ID for the user's WebSocket connection
	Producers     map[string]*Producer      // key: media_id
	Consumers     map[string]*Consumer      // key: media_id
	RtpCaps       mediasoup.RtpCapabilities // RTP capabilities of the user
}

// type RouterLink struct {
// 	FromRouterId    string // ID of the router sending the link
// 	FromTransportId string // ID of the transport sending the link
// 	ToTransportId   string // ID of the transport receiving the link
// 	ToRouterId      string // ID of the router receiving the link
// }

type Room struct {
	RoomId     string               // Unique identifier for the room
	RoomName   string               // Name of the room
	OwnerId    string               // User ID of the room owner
	Users      map[string]*RoomUser // key: user_id
	Router     *Router              // Router associated with the room
	MediaMgr   *MediaManager        // Media manager for handling media operations
	Dispatcher RoomDispatcher       // Message dispatcher for handling room-related messages
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func NewRoom(rid, roomId, roomName, ownerId string, mediaMgr *MediaManager,
	dispatcher RoomDispatcher) *Room {
	routerData, err := mediaMgr.CreateRouter(rid, roomId)
	if err != nil {
		LOG(roomId).WithError(err).Error("failed to create router")
		return nil
	}

	router := &Router{
		RouterId:  routerData.RouterId,
		RoomId:    roomId,
		ServiceId: routerData.ServiceId,
		WorkerId:  routerData.WorkerId,
		RtpCaps:   *routerData.RtpCaps,
	}

	room := &Room{
		RoomId:     roomId,
		RoomName:   roomName,
		OwnerId:    ownerId,
		Users:      make(map[string]*RoomUser),
		Router:     router,
		MediaMgr:   mediaMgr,
		Dispatcher: dispatcher,
	}

	return room
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) Destroy() error {
	if r.Router != nil {
		closeReq := common.Message{
			Cmd: common.CLOSE_ROUTER_REQ,
			Rid: common.GenerateRandomStr(8),
			Data: common.CloseRouterReqData{
				RouterId:  r.Router.RouterId,
				ServiceId: r.Router.ServiceId,
				WorkerId:  r.Router.WorkerId,
			},
		}

		connId, err := r.MediaMgr.GetServiceConnection(r.Router.ServiceId)
		if err != nil {
			return errors.New("failed to get media service connection: " + err.Error())
		}

		res, err := r.Dispatcher.DispatchSync(closeReq, connId, common.CLOSE_ROUTER_RES)
		if err != nil {
			return errors.New("failed to dispatch close router request: " + err.Error())
		}

		var resData common.ResMsgData
		if err := mapstructure.Decode(res.Data, &resData); err != nil {
			return errors.New("failed to decode response data: " + err.Error())
		}

		var closeResData common.CloseRouterResData
		if err := mapstructure.Decode(resData.Data, &closeResData); err != nil {
			return errors.New("failed to decode close router response data: " + err.Error())
		}

		if resData.Code != 0 {
			return errors.New("failed to close router: " + resData.Msg)
		}
	}

	r.Users = make(map[string]*RoomUser)

	LOG().Info("room destroyed: " + r.RoomId)

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) GetUserList() []RoomUser {
	userList := make([]RoomUser, 0, len(r.Users))

	for _, user := range r.Users {
		userList = append(userList, *user)
	}

	return userList
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) HasUser(userId string) bool {
	_, exists := r.Users[userId]
	return exists
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) IsEmpty() bool {
	return len(r.Users) == 0
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) GetUser(userId string) (*RoomUser, error) {
	user, exists := r.Users[userId]
	if !exists {
		return nil, errors.New("user does not exist in the room")
	}

	return user, nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) AddUser(user RoomUser) error {
	if _, exists := r.Users[user.UserId]; exists {
		return errors.New("user already exists in the room")
	}

	r.Users[user.UserId] = &user

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) RemoveUser(userId string) {
	user, exists := r.Users[userId]
	if !exists {
		return
	}

	// Close producers if they exist
	for _, producer := range user.Producers {
		err := r.DoCloseProducer(r.RoomId, user.SendTransport.TransportId, producer.ProducerId)
		if err != nil {
			LOG().WithError(err).Error("failed to close producer")
		}
	}

	// Close consumers if they exist
	for _, consumer := range user.Consumers {
		err := r.DoCloseConsumer(r.RoomId, user.RecvTransport.TransportId, consumer.ConsumerId)
		if err != nil {
			LOG().WithError(err).Error("failed to close consumer")
		}
	}

	// Close send transport
	if user.SendTransport != nil {
		if err := r.CloseTransport(r.RoomId, user.SendTransport.TransportId); err != nil {
			LOG().WithError(err).Error("failed to close send transport")
		}
	}

	// Close receive transport
	if user.RecvTransport != nil {
		if err := r.CloseTransport(r.RoomId, user.RecvTransport.TransportId); err != nil {
			LOG().WithError(err).Error("failed to close receive transport")
		}
	}

	delete(r.Users, userId)
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) ProducerExists(mediaId string) bool {
	for _, user := range r.Users {
		if _, exists := user.Producers[mediaId]; exists {
			return true
		}
	}
	return false
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) ConsumerExists(userId string, mediaId string) bool {
	user, exists := r.Users[userId]
	if !exists {
		return false
	}

	_, exists = user.Consumers[mediaId]
	return exists
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) CreateProducer(rid string, data common.SFUStartProduceReqData) (*Producer, error) {
	user, exists := r.Users[data.UserId]
	if !exists {
		return nil, errors.New("user does not exist in the room")
	}

	if _, exists := user.Producers[data.Media.MediaId]; exists {
		return nil, errors.New("media stream already exists for this user")
	}

	// Create a new producer in mediasoup
	createProducerReq := common.Message{
		Cmd: common.CREATE_PRODUCER_REQ,
		Rid: rid,
		Data: common.CreateProducerReqData{
			RouterId:      r.Router.RouterId,
			TransportId:   user.SendTransport.TransportId,
			Kind:          mediasoup.MediaKind(data.Media.MediaType),
			RtpParameters: data.RtpParameters,
			AppData:       data.AppData,
		},
	}

	connId, err := r.MediaMgr.GetServiceConnection(r.Router.ServiceId)
	if err != nil {
		return nil, errors.New("failed to get service connection for router: " + err.Error())
	}

	res, err := r.Dispatcher.DispatchSync(
		createProducerReq,
		connId,
		common.CREATE_PRODUCER_RES,
	)
	if err != nil {
		return nil, errors.New("failed to dispatch create producer request: " + err.Error())
	}

	var resData common.ResMsgData
	if err := mapstructure.Decode(res.Data, &resData); err != nil {
		return nil, errors.New("failed to decode response data: " + err.Error())
	}

	var produceResData common.CreateProducerResData
	if err := mapstructure.Decode(resData.Data, &produceResData); err != nil {
		return nil, errors.New("failed to decode create producer response data: " + err.Error())
	}

	producer := &Producer{
		ProducerId:    produceResData.ProducerId,
		Media:         data.Media,
		RtpParameters: *data.RtpParameters,
		Producing:     false,
		CreatedAt:     time.Now().Unix(),
	}

	user.Producers[data.Media.MediaId] = producer

	return producer, nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) DoCloseProducer(rid string, transportId string, producerId string) error {
	closeProducerReq := common.Message{
		Cmd: common.CLOSE_PRODUCER_REQ,
		Rid: rid,
		Data: common.CloseProducerReqData{
			RouterId:    r.Router.RouterId,
			TransportId: transportId,
			ProducerId:  producerId,
		},
	}

	connId, err := r.MediaMgr.GetServiceConnection(r.Router.ServiceId)
	if err != nil {
		return errors.New("failed to get service connection for router: " + err.Error())
	}

	res, err := r.Dispatcher.DispatchSync(closeProducerReq, connId, common.CLOSE_PRODUCER_RES)
	if err != nil {
		return errors.New("failed to dispatch close producer request: " + err.Error())
	}

	var resData common.ResMsgData
	if err := mapstructure.Decode(res.Data, &resData); err != nil {
		return errors.New("failed to decode response data: " + err.Error())
	}

	if resData.Code != 0 {
		return errors.New("failed to close producer: " + resData.Msg)
	}

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) CloseProducer(rid string, data common.SFUStopProduceReqData) error {
	user, exists := r.Users[data.UserId]
	if !exists {
		return errors.New("user does not exist in the room")
	}

	producer, exists := user.Producers[data.Media.MediaId]
	if !exists {
		return errors.New("producer does not exist for this media stream")
	}

	if err := r.DoCloseProducer(rid, user.SendTransport.TransportId, producer.ProducerId); err != nil {
		LOG(rid).WithError(err).Error("failed to close producer")
		return err
	}

	delete(user.Producers, data.Media.MediaId)

	LOG(rid).WithFields(logrus.Fields{
		"room_id": data.RoomId,
		"user_id": data.UserId,
		"media":   data.Media,
	}).Info("closed producer")

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) CreateConsumer(rid string, data common.SFUStartSubscribeReqData) (*Consumer, error) {
	user, exists := r.Users[data.UserId]
	if !exists {
		return nil, errors.New("user does not exist in the room")
	}

	if _, exists := user.Consumers[data.Media.MediaId]; exists {
		return nil, errors.New("consumer already exists for this media stream")
	}

	if user.RecvTransport == nil {
		return nil, errors.New("receive transport does not exist for the user in the room")
	}

	pubUser, exists := r.Users[data.PubUserId]
	if !exists {
		return nil, errors.New("producer user does not exist in the room")
	}

	producer, exists := pubUser.Producers[data.Media.MediaId]
	if !exists {
		return nil, errors.New("producer does not exist for this media stream")
	}

	// Create consumer in mediasoup
	createConsumerReq := common.CreateConsumerReqData{
		RouterId:    r.Router.RouterId,
		TransportId: user.RecvTransport.TransportId,
		ProducerId:  producer.ProducerId,
		Kind:        mediasoup.MediaKind(data.Media.MediaType),
		RtpCaps:     &user.RtpCaps,
		AppData:     mediasoup.H{"room_id": data.RoomId},
	}

	createConsumerMsg := common.Message{
		Cmd:  common.CREATE_CONSUMER_REQ,
		Rid:  rid,
		Data: createConsumerReq,
	}

	connId, err := r.MediaMgr.GetServiceConnection(r.Router.ServiceId)
	if err != nil {
		return nil, errors.New("failed to get service connection for router: " + err.Error())
	}

	res, err := r.Dispatcher.DispatchSync(createConsumerMsg, connId, common.CREATE_CONSUMER_RES)
	if err != nil {
		return nil, errors.New("failed to dispatch create consumer request: " + err.Error())
	}

	var resData common.ResMsgData
	if err := mapstructure.Decode(res.Data, &resData); err != nil {
		return nil, errors.New("failed to decode response data: " + err.Error())
	}

	if resData.Code != 0 {
		return nil, errors.New("failed to create consumer: " + resData.Msg)
	}

	var consumeResData common.CreateConsumerResData
	if err := mapstructure.Decode(resData.Data, &consumeResData); err != nil {
		return nil, errors.New("failed to decode create consumer response data: " + err.Error())
	}

	consumer := &Consumer{
		ProduceUserId: data.PubUserId,
		Media:         data.Media,
		CreatedAt:     time.Now().Unix(),
		ProducerId:    producer.ProducerId,
		ConsumerId:    consumeResData.ConsumerId,
		RtpParameters: *consumeResData.RtpParameters,
	}

	user.Consumers[data.Media.MediaId] = consumer

	return consumer, nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) DoCloseConsumer(rid string, transportId string, consumerId string) error {

	// Notify media service to close the consumer
	closeConsumerReq := common.Message{
		Cmd: common.CLOSE_CONSUMER_REQ,
		Rid: rid,
		Data: common.CloseConsumerReqData{
			RouterId:    r.Router.RouterId,
			TransportId: transportId,
			ConsumerId:  consumerId,
		},
	}

	connId, err := r.MediaMgr.GetServiceConnection(r.Router.ServiceId)
	if err != nil {
		return errors.New("failed to get service connection for router: " + err.Error())
	}

	res, err := r.Dispatcher.DispatchSync(closeConsumerReq, connId, common.CLOSE_CONSUMER_RES)
	if err != nil {
		return errors.New("failed to dispatch close consumer request: " + err.Error())
	}

	var resData common.ResMsgData
	if err := mapstructure.Decode(res.Data, &resData); err != nil {
		return errors.New("failed to decode response data: " + err.Error())
	}

	if resData.Code != 0 {
		return errors.New("failed to close consumer: " + resData.Msg)
	}

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) CloseConsumer(rid string, data common.SFUStopSubscribeReqData) error {
	user, exists := r.Users[data.UserId]
	if !exists {
		return errors.New("user does not exist in the room")
	}

	consumer, exists := user.Consumers[data.Media.MediaId]
	if !exists {
		return errors.New("consumer does not exist for this media stream")
	}

	if err := r.DoCloseConsumer(rid, user.RecvTransport.TransportId, consumer.ConsumerId); err != nil {
		LOG(rid).WithError(err).Error("failed to close consumer")
		return err
	}

	delete(user.Consumers, data.Media.MediaId)

	LOG(rid).WithFields(logrus.Fields{
		"room_id": data.RoomId,
		"user_id": data.UserId,
		"media":   data.Media,
	}).Info("closed consumer")

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) GetProducer(userId string, mediaId string) (*Producer, error) {
	user, exists := r.Users[userId]
	if !exists {
		return nil, errors.New("user does not exist in the room")
	}

	producer, exists := user.Producers[mediaId]
	if !exists {
		return nil, errors.New("producer does not exist for this media stream")
	}

	return producer, nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) GetConsumer(userId string, mediaId string) (*Consumer, error) {
	user, exists := r.Users[userId]
	if !exists {
		return nil, errors.New("user does not exist in the room")
	}

	consumer, exists := user.Consumers[mediaId]
	if !exists {
		return nil, errors.New("consumer does not exist for this media stream")
	}

	return consumer, nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) SendTransportExists(userId string) bool {
	user, exists := r.Users[userId]
	if !exists {
		return false
	}

	return user.SendTransport != nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) RecvTransportExists(userId string) bool {
	user, exists := r.Users[userId]
	if !exists {
		return false
	}

	return user.RecvTransport != nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) MatchRecvTransport(userId string, transportId string) bool {
	user, exists := r.Users[userId]
	if !exists {
		return false
	}

	if user.RecvTransport == nil {
		return false
	}

	return user.RecvTransport.TransportId == transportId
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) MatchSendTransport(userId string, transportId string) bool {
	user, exists := r.Users[userId]
	if !exists {
		return false
	}

	if user.SendTransport == nil {
		return false
	}

	return user.SendTransport.TransportId == transportId
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) CreateTransport(rid string, data common.SFUCreateTransportReqData) (
	*Transport, error) {
	user := r.Users[data.UserId]
	if user == nil {
		return nil, errors.New("user not found in the room")
	}

	serviceConnId, err := r.MediaMgr.GetServiceConnection(r.Router.ServiceId)
	if err != nil {
		return nil, errors.New("failed to get media service connection")
	}

	// Create webrtc transport from media service
	createReq := common.Message{
		Cmd: common.CREATE_TRANSPORT_REQ,
		Rid: rid,
		Data: common.CreateTransportReqData{
			RouterId:      r.Router.RouterId,
			TransportType: data.TransportType,
			ForceTcp:      data.ForceTcp,
			Producing:     data.Producing,
			Consuming:     data.Consuming,
			SctpCaps:      data.SctpCaps,
		},
	}

	// 同步请求，等待 media service 返回结果
	mediaRes, err := r.Dispatcher.DispatchSync(createReq, serviceConnId,
		common.CREATE_TRANSPORT_RES,
	)
	if err != nil {
		return nil, err
	}

	var resData common.ResMsgData
	if err := mapstructure.Decode(mediaRes.Data, &resData); err != nil {
		return nil, err
	}

	if resData.Code != 0 {
		return nil, errors.New("failed to create webrtc transport: " + resData.Msg)
	}

	var createResData common.CreateTransportResData
	if err := mapstructure.Decode(resData.Data, &createResData); err != nil {
		return nil, err
	}

	transport := &Transport{
		TransportType:  "webrtc",
		TransportId:    createResData.TransportId,
		IceParameters:  createResData.IceParameters,
		IceCandidates:  createResData.IceCandidates,
		DtlsParameters: createResData.DtlsParameters,
		SctpParameters: createResData.SctpParameters,
	}

	if data.Producing {
		user.SendTransport = transport
	} else {
		user.RecvTransport = transport
	}

	return transport, nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) ConnectTransport(rid string, data common.SFUConnectTransportReqData) error {
	serviceConnId, err := r.MediaMgr.GetServiceConnection(r.Router.ServiceId)
	if err != nil {
		return errors.New("failed to get media service connection: " + err.Error())
	}

	connectReq := common.Message{
		Cmd: common.CONNECT_TRANSPORT_REQ,
		Rid: rid,
		Data: common.ConnectTransportReqData{
			RouterId:       r.Router.RouterId,
			TransportType:  data.TransportType,
			TransportId:    data.TransportId,
			DtlsParameters: data.DtlsParameters,
		},
	}

	connectRes, err := r.Dispatcher.DispatchSync(connectReq, serviceConnId,
		common.CONNECT_TRANSPORT_RES,
	)
	if err != nil {
		return errors.New("failed to dispatch connect transport request: " + err.Error())
	}

	var resData common.ResMsgData
	if err := mapstructure.Decode(connectRes.Data, &resData); err != nil {
		return errors.New("failed to decode connect transport response data: " + err.Error())
	}

	if resData.Code != 0 {
		return errors.New("connect transport failed: " + resData.Msg)
	}

	common.Logger(common.SERVICE_TYPE_ROOM, rid).WithFields(logrus.Fields{
		"room_id": data.RoomId,
		"user_id": data.UserId,
	}).Info("Connect transport response sent successfully")

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (r *Room) CloseTransport(rid string, transportId string) error {
	serviceConnId, err := r.MediaMgr.GetServiceConnection(r.Router.ServiceId)
	if err != nil {
		return errors.New("failed to get media service connection: " + err.Error())
	}

	closeReq := common.Message{
		Cmd: common.CLOSE_TRANSPORT_REQ,
		Rid: rid,
		Data: common.CloseTransportReqData{
			RouterId:    r.Router.RouterId,
			TransportId: transportId,
		},
	}

	closeRes, err := r.Dispatcher.DispatchSync(closeReq, serviceConnId,
		common.CLOSE_TRANSPORT_RES,
	)
	if err != nil {
		return errors.New("failed to dispatch close transport request: " + err.Error())
	}

	var resData common.ResMsgData
	if err := mapstructure.Decode(closeRes.Data, &resData); err != nil {
		return errors.New("failed to decode close transport response data: " + err.Error())
	}

	if resData.Code != 0 {
		return errors.New("close transport failed: " + resData.Msg)
	}

	LOG(rid).WithFields(logrus.Fields{
		"room_id":      r.RoomId,
		"transport_id": transportId,
	}).Info("Close transport successfully")

	return nil
}
